package generatorImplementations.hashing;

import generator.Generator;
import generator.GeneratorType;
import generator.properties.AnimationPropertiesContainer;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Locale;

//import sun.rmi.runtime.NewThreadAction;

import algoanim.animalscript.AnimalScript;
import algoanim.primitives.ArrayMarker;
import algoanim.primitives.IntArray;
import algoanim.primitives.Rect;
import algoanim.primitives.Text;
import algoanim.primitives.generators.Language;
import algoanim.properties.AnimationPropertiesKeys;
import algoanim.properties.ArrayMarkerProperties;
import algoanim.properties.ArrayProperties;
import algoanim.properties.RectProperties;
import algoanim.properties.TextProperties;
import algoanim.util.Coordinates;
import algoanim.util.Offset;
import algoanim.util.TicksTiming;
import algoanim.util.Timing;

public class LinearHashing6 implements Generator {

	static class SimpleFileWriter {
		public static void writeFile(File selSort, String data) {
			try {
				FileWriter fw = new FileWriter(selSort);
				BufferedWriter bw = new BufferedWriter(fw);
				bw.write(data);
				bw.close();
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private Language lang;
	private ArrayProperties arrayProperties;
	private int n;
	private IntArray eingabeArray;
	private IntArray ausgabeArray;
	private ArrayMarkerProperties arrayMarkerProperties;
	private ArrayMarker arrayMarker;
	private Text hashFunktionText, hashErgebnisText, kollisionsFunktionText,
			kollisionsErgebnisText, kollisionsText;
	private Timing defaultTiming;
	private int[] arrayContents;
	private Rect background;

	private int a;
	private int b;
	private int showCounter;

	private void setup() {
		arrayProperties = new ArrayProperties();
		TextProperties textProperties = new TextProperties();
		textProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		textProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		Text header = lang.newText(new Coordinates(20, 30), "Lineares Hashing",
				"header", null, textProperties);

		RectProperties rectProperties = new RectProperties();
		rectProperties.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.GRAY);
		rectProperties.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		rectProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 2);
//		Rect headerBackground = lang.newRect(new Offset(-5, -5, header,
//				AnimalScript.DIRECTION_NW), new Offset(5, 5, header,
//				AnimalScript.DIRECTION_SE), "HeaderBackground", null,
//				rectProperties);
		background = lang.newRect(new Offset(-5, -5, header,
				AnimalScript.DIRECTION_NW), new Offset(5, 5, header,
				AnimalScript.DIRECTION_SE), "HeaderBackground", null,
				rectProperties);

		defaultTiming = new TicksTiming(100);

	}

	public void init() {
		lang = new AnimalScript("LinearesHashing",
				"Johannes Born und Tuba G&ouml;zel", 640, 480);
		lang.setStepMode(true);
	}

	private void runHashing(int[] eingabe) {
		int n = eingabe.length;
		hashFunktionText.setText("Hashfunktion: h(x) = " + a + " * x + " + b
				+ " mod " + n + "", null, defaultTiming);
		kollisionsFunktionText.setText(
				"Kollisionsfunktion: h'(x) = (h(x) + i) mod " + n, null,
				defaultTiming);
		lang.nextStep();

		int hashWert;
		for (int i = 0; i < n; i++) {
			eingabeArray.highlightCell(i, null, defaultTiming);
			int input = eingabe[i];
			if (input != 0) {
				hashWert = hashFunktion(input);
				if (hashWert != -1) {
					ausgabeArray.put(hashWert, input, null, null);
					ausgabeArray.highlightCell(hashWert, null, defaultTiming);
					lang.nextStep();

					for (int j = 0; j <= showCounter; j++) {
						kollisionsErgebnisText.hide();
					}
					showCounter = 0;
					eingabeArray.unhighlightCell(i, null, defaultTiming);
					kollisionsText.hide(defaultTiming);
				}
			}

		}
	}

	private int hashFunktion(int input) {
		int hashWert = (a * input + b) % n;
		hashErgebnisText.setText("Berechnung der Hashfunktion mit x = " + input
				+ " ergibt h(x) = " + hashWert, null, defaultTiming);
		lang.nextStep();

		if (ausgabeArray.getData(hashWert) == 0) {
			return (a * input + b) % n;
		} else {
			kollisionsText.setText(
					"Position besetzt. Kollisionsfunktion wird aufgerufen.",
					null, null);
			kollisionsText.show();
			arrayMarker.move(hashWert, null, null);
			return kollisionsHashFunktion(input, hashWert);
		}
	}

	private int kollisionsHashFunktion(int input, int hashWert) {
		int kollisionsHashWert;
		for (int i = 1; i < n; i++) {
			kollisionsHashWert = (hashWert + i) % n;
			arrayMarker.move(kollisionsHashWert, null, defaultTiming);
			kollisionsErgebnisText.setText(
					"Berechnung der Kollisionsfunktion mit i = " + i
							+ " ergibt h'(x) = " + kollisionsHashWert, null,
					defaultTiming);
			kollisionsErgebnisText.show();
			showCounter++;
			lang.nextStep();

			if (ausgabeArray.getData(kollisionsHashWert) == 0) {
				return kollisionsHashWert;
			}
		}
		return -1;
	}

	private void initForMain() {
		arrayContents = new int[] { 457, 203, 105, 154, 216, 112, 981 };
		n = arrayContents.length;
		a = 3;
		b = 5;
		arrayProperties.set(AnimationPropertiesKeys.ELEMENTCOLOR_PROPERTY,
				Color.RED);
		arrayProperties.set(AnimationPropertiesKeys.CELLHIGHLIGHT_PROPERTY,
				Color.YELLOW);
		arrayProperties.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		arrayProperties.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.WHITE);
		initForGenerate();
		runHashing(arrayContents);

	}


	public static void main(String[] args) {
		LinearHashing6 linearHashing = new LinearHashing6();
		linearHashing.init();
		linearHashing.initForMain();
//		linearHashing.runHashing(linearHashing.arrayContents);
		System.out.println(linearHashing.lang.toString());
		System.out.println(linearHashing.getCodeExample());
		SimpleFileWriter.writeFile(new File(
				"src/generatorImplementations/hashing/LinearHashing6.asu"),
				linearHashing.lang.getAnimationCode());
	}
	
	private void initForGenerate() {

		eingabeArray = lang.newIntArray(new Coordinates(50, 100),
				arrayContents, "array", null, arrayProperties);
		ausgabeArray = lang.newIntArray(new Coordinates(50, 200), new int[n],
				"array", null, arrayProperties);
		hashFunktionText = lang.newText(new Offset(100, 0, eingabeArray,
				AnimalScript.DIRECTION_NE), "", "", null);
		hashErgebnisText = lang.newText(new Offset(10, 5, hashFunktionText,
				AnimalScript.DIRECTION_SW), "", "text", null);
		kollisionsText = lang.newText(new Offset(0, 5, hashErgebnisText,
				AnimalScript.DIRECTION_SW), "", "", null);
		kollisionsFunktionText = lang.newText(new Offset(-10, 20,
				kollisionsText, AnimalScript.DIRECTION_SW), "", "", null);
		kollisionsErgebnisText = lang.newText(new Offset(10, 5,
				kollisionsFunktionText, AnimalScript.DIRECTION_SW), "", "",
				null);

		arrayMarkerProperties = new ArrayMarkerProperties();
		arrayMarkerProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY,
				Color.BLUE);
		arrayMarkerProperties.set(AnimationPropertiesKeys.LABEL_PROPERTY, "");

		arrayMarker = lang.newArrayMarker(ausgabeArray, 0, "", null,
				arrayMarkerProperties);
		arrayMarker.hide(defaultTiming);
	}


	public String generate(AnimationPropertiesContainer arg0,
			Hashtable<String, Object> arg1) {
		init();
		setup();
		arrayContents = (int[]) arg1.get("eingabe");
		n = arrayContents.length;
		a = (Integer) arg1.get("a - das Skalar");
		b = (Integer) arg1.get("b - die Konstante");
		
		arrayProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, arg0.get(
				"eingabe", AnimationPropertiesKeys.COLOR_PROPERTY));
		arrayProperties.set(AnimationPropertiesKeys.FILL_PROPERTY, arg0.get(
				"eingabe", AnimationPropertiesKeys.FILL_PROPERTY));
		arrayProperties.set(AnimationPropertiesKeys.FILLED_PROPERTY, arg0.get(
				"eingabe", AnimationPropertiesKeys.FILLED_PROPERTY));
		arrayProperties.set(AnimationPropertiesKeys.CELLHIGHLIGHT_PROPERTY,
				arg0.get("eingabe",
						AnimationPropertiesKeys.CELLHIGHLIGHT_PROPERTY));

		initForGenerate();
		runHashing(arrayContents);
		return lang.toString();
	}

	@Override
	public String getAlgorithmName() {
		return "Linear Sondierendes Hashing";
	}

	@Override
	public String getAnimationAuthor() {
		return "Johannes Born and Tuba G&ouml;zel";
	}

	@Override
	public String getCodeExample() {

		return "private int[] ausgabe;<br /><br />"
				+ "public void runHashing(int[] eingabe) {<br />"
				+ "  int n = eingabe.length;<br />"
				+ "  int[] ausgabe = new int[n];<br />"
				+ "  int hashWert;<br />"
				+ "  for (int i = 0; i < n; i++) {<br />"
				+ "    int input = eingabe[i];<br />"
				+ "    if (input != 0) {<br />"
				+ "      hashWert = hashFunktion(input);<br />"
				+ "      if (hashWert != -1) <br />"
				+ "        ausgabe[hashWert] = input;<br />"
				+ "    }<br />"
				+ "  }<br />"
				+ "}<br />"
				+ "<br />"
				+ "private int hashFunktion(int input) {<br />"
				+ "  int hashWert = (a * input + 5) % n;<br />"
				+ "  if (ausgabe[hashWert] == 0)<br />"
				+ "    return (a * input + b) % n;<br />"
				+ "  else<br />"
				+ "    return kollisionsHashFunktion(input, hashWert);<br />"
				+ "}<br />"
				+ "<br />"
				+ "private int kollisionsHashFunktion(int input, int hashWert) {<br />"
				+ "  int kollisionsHashWert;<br />"
				+ "  for (int i = 1; i &lt n; i++) {<br />"
				+ "    kollisionsHashWert = (hashWert + i) % n;<br />"
				+ "    if (ausgabe[kollisionsHashWert] == 0)<br />"
				+ "      return kollisionsHashWert;<br />" + "  }<br />"
				+ "  return -1;\n" + "}";
	}

	@Override
	public Locale getContentLocale() {
		return Locale.GERMANY;
	}

	@Override
	public String getDescription() {
		return "Das Hash-Verfahren wird verwendet, um Datenstrukturen abzulegen, "
				+ "damit sie schneller gespeichert, gefunden und gel&ouml;scht werden "
				+ "k&ouml;nnen. Eines von den mehreren Hash-Verfahren ist das Lineares "
				+ "Hashing. Hier werden die Daten nach einer linearen Funktion gehasht."
				+ "<br /><br />"
				+ "Hashfunktion:h(x) = (ax+b) mod n <br />"
				+ "Kollisionsfunktion:h'(x) = (h(x) + i) mod n<br /><br />"
				+ "Eingabeparameter:<br />"
				+ "- eingabe: x Werte, die in nacheinander in der Hashfunktion aufgerufen werden<br />"
				+ "- a: das Skalar bei Hashfunktion<br />"
				+ "- b: die Konstante bei Hashfunktion";
	}

	@Override
	public String getFileExtension() {
		return ".asu";
	}

	@Override
	public GeneratorType getGeneratorType() {
		return new GeneratorType(GeneratorType.GENERATOR_TYPE_HASHING);
	}

	@Override
	public String getName() {
		return "LinearHashingDemo";
	}

	@Override
	public String getOutputLanguage() {
		return Generator.JAVA_OUTPUT;
	}
}
